#ifndef __TPS_XML_SERIALIZER_H__
#define __TPS_XML_SERIALIZER_H__

#include "private/tps_tl.h"
#include "private/tps_non_copyable.h"
#include "private/tps_utf8_converter.h"

#include "private/rapidxml-1.13/rapidxml.hpp"
#include "private/rapidxml-1.13/rapidxml_print.hpp"

#include <string>
#include <sstream>
#include <stdexcept>
#include <iterator>
#include <iomanip>
#include <vector>
#include <map>
#include <algorithm>

namespace Tps
{

	namespace Private
	{

		template <typename T>
		struct XmlNames
		{
			static char const Packet[];
			static char const Params[];
			static char const InstId[];
			static char const ClsId[];
			static char const MtdId[];
			static char const PrmCount[];
			static char const Prm[];
			static char const IsConst[];
			static char const Id[];
			static char const Type[];
			static char const SendBy[];
			static char const Const[];
			static char const Ret[];
		};

		template <typename T> char const XmlNames<T>::Packet[] = "Packet";
		template <typename T> char const XmlNames<T>::Params[] = "Params";
		template <typename T> char const XmlNames<T>::InstId[] = "InstId";
		template <typename T> char const XmlNames<T>::ClsId[] = "ClsId";
		template <typename T> char const XmlNames<T>::MtdId[] = "MtdId";
		template <typename T> char const XmlNames<T>::PrmCount[] = "PrmCount";
		template <typename T> char const XmlNames<T>::Prm[] = "Prm";
		template <typename T> char const XmlNames<T>::IsConst[] = "IsConst";
		template <typename T> char const XmlNames<T>::Id[] = "Id";
		template <typename T> char const XmlNames<T>::Type[] = "Type";
		template <typename T> char const XmlNames<T>::SendBy[] = "SendBy";
		template <typename T> char const XmlNames<T>::Const[] = "Const";
		template <typename T> char const XmlNames<T>::Ret[] = "Ret";

		typedef TypeListBuilder
			<
				bool,
				char,
				unsigned char,
				wchar_t,
				short,
				unsigned short,
				int,
				unsigned int,
				long,
				unsigned long,
				float,
				double,
				TypeWrapper<char const *>,
				TypeWrapper<wchar_t const *>
			>
			SupportedSerializeableTypes;

		template <typename T>
		void GetParamValue(char const *strValue, T *value)
		{
			std::stringstream Io;
			Io << strValue;
			Io >> *value;
		}

		void GetParamValue(char const *strValue, char *value)
		{
			std::stringstream Io;
			Io << strValue;
			Io >> std::hex;
			unsigned Value = 0;
			Io >> Value;
			*value = Value;
		}

		void GetParamValue(char const *strValue, wchar_t *value)
		{
			*value = Utf8Converter::FromUtf8(strValue)[0];
		}

		template <typename T>
		void ValueToParam(T *value, T **ppValue, T **param)
		{
			*param = value;
		}

		template <typename T>
		void ValueToParam(T *value, T **ppValue, T ***param)
		{
			*param = ppValue;
		}

		template <typename SerializerType>
		class IParamHolder
		{
		public:
			IParamHolder(unsigned paramIndex, unsigned typeIndex, Tps::Private::RefPtrVal sendBy, bool isConst)
				: ParamIndex(paramIndex)
				, TypeIndex(typeIndex)
				, SendBy(sendBy)
				, IsConstPrm(isConst)
			{
			}

			virtual ~IParamHolder()
			{
			}

			unsigned GetParamIndex() const
			{
				return ParamIndex;
			}
			unsigned GetTypeIndex() const
			{
				return  TypeIndex;
			}
			Tps::Private::RefPtrVal GetSendBy() const
			{
				return SendBy;
			}
			bool IsConst() const
			{
				return IsConstPrm;
			}

			virtual void SaveValue(SerializerType *serializer) = 0;

		private:
			unsigned ParamIndex;
			unsigned TypeIndex;
			Tps::Private::RefPtrVal SendBy;
			bool IsConstPrm;
		};

		template <typename ParamType, typename SerializerType>
		class ParamHolder
			: public IParamHolder<SerializerType>
		{
		public:
			template <typename T>
			ParamHolder(unsigned paramIndex, unsigned typeIndex, Tps::Private::RefPtrVal sendBy, bool isConst,
						rapidxml::xml_node<> const *nodePrm, T **value)
				: IParamHolder<SerializerType>(paramIndex, typeIndex, sendBy, isConst)
				, ValuePtr(0)
				, ValuePPtr(0)
			{
				if (nodePrm->value())
				{
					GetParamValue(nodePrm->value(), &Value);
					ValuePtr = &Value;
					ValuePPtr = &ValuePtr;
					ValueToParam(ValuePtr, ValuePPtr, value);
				}
			}
			void SaveValue(SerializerType *serializer)
			{
				serializer->PutParameter(this->GetParamIndex(), this->GetTypeIndex(),
                    this->GetSendBy(), this->IsConst(), Value);
			}
		private:
			typedef typename ConstCast<typename TypeExtractor<ParamType>::Type>::Type Type;
			Type Value;
			Type *ValuePtr;
			Type **ValuePPtr;
		};

		template <typename SerializerType>
		class ParamHolder<char const *, SerializerType>
			: public IParamHolder<SerializerType>
		{
		public:
			ParamHolder(unsigned paramIndex, unsigned typeIndex, Tps::Private::RefPtrVal sendBy, bool isConst,
						rapidxml::xml_node<> const *nodePrm, char ***value)
				: IParamHolder<SerializerType>(paramIndex, typeIndex, sendBy, isConst)
				, PStr(0)
				, PPStr(0)
			{
				if (nodePrm->value())
				{
					std::string Tmp;
					rapidxml::xml_node<> const* CData = nodePrm->first_node();
					if (CData && CData->type() == rapidxml::node_cdata)
						Tmp = CData->value();
					else
						Tmp = nodePrm->value();
					if (Tmp.empty())
						Str.push_back(0);
					else
					{
						std::copy(Tmp.begin(), Tmp.end(), std::back_inserter(Str));
						if (Str.back())
							Str.push_back(0);
					}
					PStr = &Str[0];
					PPStr = &PStr;
					*value = PPStr;
				}
				else
					*value = 0;
			}
			void SaveValue(SerializerType *serializer)
			{
				serializer->PutParameter(this->GetParamIndex(), this->GetTypeIndex(), this->GetSendBy(),
                    this->IsConst(), static_cast<char const *>(&Str[0]));
			}
		private:
			std::vector<char> Str;
			char *PStr;
			char **PPStr;
		};

		template <typename SerializerType>
		class ParamHolder<wchar_t const *, SerializerType>
			: public IParamHolder<SerializerType>
		{
		public:
			ParamHolder(unsigned paramIndex, unsigned typeIndex, Tps::Private::RefPtrVal sendBy, bool isConst,
						rapidxml::xml_node<> const *nodePrm, wchar_t ***value)
				: IParamHolder<SerializerType>(paramIndex, typeIndex, sendBy, isConst)
				, PStr(0)
				, PPStr(0)
			{
				if (nodePrm->value())
				{
					std::string Src;
					rapidxml::xml_node<> const* CData = nodePrm->first_node();
					if (CData && CData->type() == rapidxml::node_cdata)
						Src = CData->value();
					else
						Src = nodePrm->value();
					std::wstring Tmp = Utf8Converter::FromUtf8(Src.c_str());
					if (Tmp.empty())
						Str.push_back(0);
					else
					{
						std::copy(Tmp.begin(), Tmp.end(), std::back_inserter(Str));
						if (Str.back())
							Str.push_back(0);
					}
					PStr = &Str[0];
					PPStr = &PStr;
					*value = PPStr;
				}
				else
					*value = 0;
			}
			void SaveValue(SerializerType *serializer)
			{
				serializer->PutParameter(this->GetParamIndex(), this->GetTypeIndex(),
                    this->GetSendBy(), this->IsConst(), static_cast<wchar_t const *>(&Str[0]));
			}
		private:
			std::vector<wchar_t> Str;
			wchar_t *PStr;
			wchar_t **PPStr;
		};
	}

	class XmlSerialization
		: private Private::NonCopyable
	{
	public:
		typedef unsigned long InstIdType;
		typedef unsigned long ClsIdType;
		typedef unsigned MtdIdType;
		typedef Tps::Private::SupportedSerializeableTypes SupportedTypes;
		typedef std::vector<char> BufferType;

	protected:
		XmlSerialization()
		{
		}
	};

	class XmlSerializer
		: public XmlSerialization
	{
		typedef Private::XmlNames<XmlSerializer> XmlNames;
	public:
		XmlSerializer()
			: Root(Document.allocate_node(rapidxml::node_element, XmlNames::Packet))
			, Params(Document.allocate_node(rapidxml::node_element, XmlNames::Params))
		{
			Document.append_node(Root);
			Root->append_node(Params);
		}
		void PutInstId(InstIdType const &instId)
		{
			AppendToRootNode(XmlNames::InstId, instId);
		}
		void PutClsId(ClsIdType const &clsId)
		{
			AppendToRootNode(XmlNames::ClsId, clsId);
		}
		void PutMtdId(MtdIdType const &mtdId)
		{
			AppendToRootNode(XmlNames::MtdId, mtdId);
		}
		void PutParamsCount(unsigned paramsCount)
		{
			AppendToRootNode(XmlNames::PrmCount, paramsCount);
		}
		void PutMtdModifier(bool isConst)
		{
			AppendToRootNode(XmlNames::IsConst, ToString(isConst ? 1 : 0));
		}
		template <typename T>
		void PutParameter(unsigned paramIndex, unsigned typeIndex, Tps::Private::RefPtrVal sendBy, bool isConst, T param)
		{
			rapidxml::xml_node<> *Param = Document.allocate_node(rapidxml::node_element, XmlNames::Prm);

			AppendParamAttr(Param, XmlNames::Id, paramIndex);
			AppendParamAttr(Param, XmlNames::Type, typeIndex);
			AppendParamAttr(Param, XmlNames::SendBy, static_cast<unsigned>(sendBy));
			AppendParamAttr(Param, XmlNames::Const, isConst ? 1 : 0);

			AppendParamValue(Param, param);

			Params->append_node(Param);
		}
		template <typename T>
		void PutRetValue(unsigned typeIndex, Tps::Private::RefPtrVal sendBy, bool isConst, T retValue)
		{
			rapidxml::xml_node<> *RetValue = Document.allocate_node(rapidxml::node_element, XmlNames::Ret);

			AppendParamAttr(RetValue, XmlNames::Type, typeIndex);
			AppendParamAttr(RetValue, XmlNames::SendBy, static_cast<unsigned>(sendBy));
			AppendParamAttr(RetValue, XmlNames::Const, isConst ? 1 : 0);

			AppendParamValue(RetValue, retValue);

			Root->append_node(RetValue);
		}

		void const* GetBuffer() const
		{
			return GetXml().c_str();
		}
		unsigned long GetBufferLen() const
		{
			return GetXml().length();
		}
		std::string const& GetXml() const
		{
			if (!XmlBuf.empty())
				return XmlBuf;
			std::stringstream Io;
			rapidxml::print(std::ostream_iterator<char>(Io), Document, rapidxml::print_no_indenting);
			XmlBuf = Io.str();
			return XmlBuf;
		}

	private:
		template <typename, typename>
		friend class Tps::Private::ParamHolder;

		rapidxml::xml_document<> Document;
		rapidxml::xml_node<> *Root;
		rapidxml::xml_node<> *Params;
		mutable std::string XmlBuf;

		template <typename T>
		std::string ToString(T const &data)
		{
			std::stringstream Io;
			Io << data;
			return Io.str();
		}
		std::string ToString(char const &data)
		{
			std::stringstream Io;
			Io << std::setbase(16) << std::setw(2) << std::setfill('0') << static_cast<unsigned>(data);
			return Io.str();
		}
		std::string ToString(wchar_t const &data)
		{
			wchar_t Tmp[] = { data, 0 };
			return Tps::Private::Utf8Converter::ToUtf8(Tmp);
		}
		template <typename T>
		void AppendToRootNode(char const *name, T const &data)
		{
			rapidxml::xml_node<> *Node = Document.allocate_node(rapidxml::node_element, name,
				Document.allocate_string(ToString(data).c_str()));
			Root->append_node(Node);
		}
		template <typename T>
		void AppendParamAttr(rapidxml::xml_node<> *node, char const *name, T const &data)
		{
			node->append_attribute(Document.allocate_attribute(name, Document.allocate_string(ToString(data).c_str())));
		}
		template <typename T>
		void AppendParamValue(rapidxml::xml_node<> *node, T const &data)
		{
			rapidxml::xml_node<> *Node = Document.allocate_node(rapidxml::node_data, 0,
				Document.allocate_string(ToString(data).c_str()));
			node->append_node(Node);
		}
		template <typename T>
		void AppendParamValue(rapidxml::xml_node<> *node, T *data)
		{
			if (!data)
				return;
			rapidxml::xml_node<> *Node = Document.allocate_node(rapidxml::node_data, 0,
				Document.allocate_string(ToString(*data).c_str()));
			node->append_node(Node);
		}
		void AppendParamValue(rapidxml::xml_node<> *node, char const *data)
		{
			if (!data)
				return;
			rapidxml::xml_node<> *Node = Document.allocate_node(rapidxml::node_cdata, 0,
				Document.allocate_string(data));
			node->append_node(Node);
		}
		void AppendParamValue(rapidxml::xml_node<> *node, wchar_t const *data)
		{
			if (!data)
				return;
			rapidxml::xml_node<> *Node = Document.allocate_node(rapidxml::node_cdata, 0,
				Document.allocate_string(ToUtf8(data).c_str()));
			node->append_node(Node);
		}
		std::string ToUtf8(wchar_t const *src) const
		{
			return Tps::Private::Utf8Converter::ToUtf8(src);
		}
	};

	class XmlDeserializer
		: public XmlSerialization
	{
		typedef Private::XmlNames<XmlDeserializer> XmlNames;
	public:
		XmlDeserializer(void const *buffer, unsigned long bufferLen)
			: Root(0)
			, Params(0)
		{
			if (!buffer || !bufferLen)
				throw std::runtime_error("Empty input buffer");

			char const *Str = reinterpret_cast<char const *>(buffer);
			Xml.assign(&Str[0], &Str[bufferLen]);

			if (Xml.back())
				Xml.push_back(0);

			Document.parse<rapidxml::parse_full>(&Xml[0]);

			if (!(Root = Document.first_node(XmlNames::Packet)))
				throw std::runtime_error("Empty Packet");

			if (!(Params = Root->first_node(XmlNames::Params)))
				throw std::runtime_error("Empty parameter list");
		}

		~XmlDeserializer()
		{
			for (ParamHolderPool::iterator i = ParamHolders.begin() ; i != ParamHolders.end() ; ++i)
				delete i->second;
		}

		InstIdType GetInstId() const
		{
			return GetFromRootNode<InstIdType>(XmlNames::InstId);
		}
		ClsIdType GetClsId() const
		{
			return GetFromRootNode<ClsIdType>(XmlNames::ClsId);
		}
		MtdIdType GetMtdId() const
		{
			return GetFromRootNode<MtdIdType>(XmlNames::MtdId);
		}
		unsigned GetParamsCount() const
		{
			return GetFromRootNode<unsigned>(XmlNames::PrmCount);
		}
		bool IsConst() const
		{
			return !!GetFromRootNode<int>(XmlNames::IsConst);
		}

		template <typename OriginalParamType, typename T>
		void GetParameter(unsigned paramIndex, unsigned typeIndex, Tps::Private::RefPtrVal sendBy, bool isConst, T **param)
		{
			rapidxml::xml_node<> *Prm = GetParamNode(paramIndex);
			if (!Prm)
				throw std::runtime_error("Bad parameter index");

			if (FromString<unsigned>(GetParamAttr(Prm, XmlNames::Type).c_str()) != typeIndex)
				throw std::runtime_error("Bad parameter type");

			if (static_cast<Tps::Private::RefPtrVal>(FromString<unsigned>(GetParamAttr(Prm, XmlNames::SendBy).c_str())) != sendBy)
				throw std::runtime_error("Bad parameter");

			if (!!FromString<unsigned>(GetParamAttr(Prm, XmlNames::Const).c_str()) != isConst)
				throw std::runtime_error("Bad cv for parameter");

			std::auto_ptr<Tps::Private::IParamHolder<XmlSerializer> > ParamHolder(
				new Tps::Private::ParamHolder<OriginalParamType, XmlSerializer>(
					paramIndex, typeIndex, sendBy, isConst, Prm, param
				));
			ParamHolderPool::iterator Iter = ParamHolders.find(paramIndex);
			if (Iter != ParamHolders.end())
			{
				delete Iter->second;
				ParamHolders.erase(Iter);
			}
			ParamHolders[paramIndex] = ParamHolder.release();
		}
        
        template <typename OriginalRetType, typename T>
		void GetRetValue(unsigned typeIndex, Tps::Private::RefPtrVal sendBy, bool isConst, T **ret)
        {
            rapidxml::xml_node<> *Ret = Root->first_node(XmlNames::Ret);
			if (!Ret)
				throw std::runtime_error("No ret value");

			if (FromString<unsigned>(GetParamAttr(Ret, XmlNames::Type).c_str()) != typeIndex)
				throw std::runtime_error("Bad ret value type");

			if (static_cast<Tps::Private::RefPtrVal>(FromString<unsigned>(GetParamAttr(Ret, XmlNames::SendBy).c_str())) != sendBy)
				throw std::runtime_error("Bad ret value");

			if (!!FromString<unsigned>(GetParamAttr(Ret, XmlNames::Const).c_str()) != isConst)
				throw std::runtime_error("Bad cv for ret value");

			std::auto_ptr<Tps::Private::IParamHolder<XmlSerializer> > RetValueHolder(
				new Tps::Private::ParamHolder<OriginalRetType, XmlSerializer>(
					0, typeIndex, sendBy, isConst, Ret, ret
				));
            RetValue = RetValueHolder;
        }

		template <typename PackerType>
		void Pack(PackerType *serializer) const
		{
			for (ParamHolderPool::const_iterator i = ParamHolders.begin() ; i != ParamHolders.end() ; ++i)
				i->second->SaveValue(serializer);
		}

	private:
		std::vector<char> Xml;
		rapidxml::xml_document<> Document;
		rapidxml::xml_node<> *Root;
		rapidxml::xml_node<> *Params;
		typedef std::map<unsigned, Tps::Private::IParamHolder<XmlSerializer> *> ParamHolderPool;
		ParamHolderPool ParamHolders;
        std::auto_ptr<Tps::Private::IParamHolder<XmlSerializer> > RetValue;

		template <typename T>
		T GetFromRootNode(char const *name) const
		{
			rapidxml::xml_node<> *Node = Root->first_node(name);
			if (!Node)
				throw std::runtime_error("Value not found");
			if (!Node->value())
				throw std::runtime_error("Empty value");
			std::stringstream Io;
			Io << Node->value();
			T Value = T();
			Io >> Value;
			return Value;
		}

		template <typename T>
		T FromString(char const *str) const
		{
			std::stringstream Io;
			Io << str;
			T Value;
			Io >> Value;
			return Value;
		}

		std::string GetParamAttr(rapidxml::xml_node<> const *prm, char const *attrName) const
		{
			rapidxml::xml_attribute<> *Attr = prm->first_attribute(attrName);
			if (!Attr)
				throw std::runtime_error("Attribute not found");
			if (!Attr->value())
				throw std::runtime_error("Empty attribute");
			return Attr->value();
		}

		rapidxml::xml_node<>* GetParamNode(unsigned index) const
		{
			for (rapidxml::xml_node<> *i = Params->first_node(XmlNames::Prm) ; i ; i = i->next_sibling(XmlNames::Prm))
			{
				if (FromString<unsigned>(GetParamAttr(i, XmlNames::Id).c_str()) == index)
					return i;
			}
			return 0;
		}
	};

}

#endif	// !__TPS_XML_SERIALIZER_H__
